// IMPORTANT: Read the license included with this code archive.

namespace Hosting
{
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.ComponentModel.Design.Serialization;

    [ProvideProperty("Name", typeof(IComponent))]
    internal class DesignerHost : IDesignerHost,
                                  IContainer,
                                  IComponentChangeService,
                                  IExtenderProvider,
                                  ITypeDescriptorFilterService,
                                  IExtenderListService,
                                  IExtenderProviderService
    {
        // Transactions
        private readonly Stack transactions;

        // Services
        private readonly IServiceContainer parent;

        // Container
        private readonly Hashtable components;

        private readonly Hashtable designers;

        private readonly ArrayList extenderProviders;

        private IComponent rootComponent;

        public DesignerHost(IServiceContainer parent)
        {
            // Keep the parent reference around for re-use
            this.parent = parent;

            // Initialise container helpers
            this.components = new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
            this.designers = new Hashtable();

            // Initialise transaction stack
            this.transactions = new Stack();

            // Add our own services
            parent.AddService(typeof(IDesignerHost), this);
            parent.AddService(typeof(IContainer), this);
            parent.AddService(typeof(IComponentChangeService), this);
            parent.AddService(typeof(ITypeDescriptorFilterService), this);

            // Add extender services
            this.extenderProviders = new ArrayList();
            parent.AddService(typeof(IExtenderListService), this);
            parent.AddService(typeof(IExtenderProviderService), this);
            this.AddExtenderProvider(this);

            // Add selection service
            parent.AddService(typeof(ISelectionService), new SelectionService(this));
        }

        #region IServiceContainer Implementation

        public object GetService(System.Type serviceType)
        {
            return this.parent.GetService(serviceType);
        }

        public void AddService(
            System.Type serviceType, System.ComponentModel.Design.ServiceCreatorCallback callback, bool promote)
        {
            this.parent.AddService(serviceType, callback, promote);
        }

        public void AddService(System.Type serviceType, System.ComponentModel.Design.ServiceCreatorCallback callback)
        {
            this.parent.AddService(serviceType, callback);
        }

        public void AddService(System.Type serviceType, object serviceInstance, bool promote)
        {
            this.parent.AddService(serviceType, serviceInstance, promote);
        }

        public void AddService(System.Type serviceType, object serviceInstance)
        {
            this.parent.AddService(serviceType, serviceInstance);
        }

        public void RemoveService(System.Type serviceType, bool promote)
        {
            this.parent.RemoveService(serviceType, promote);
        }

        public void RemoveService(System.Type serviceType)
        {
            this.parent.RemoveService(serviceType);
        }

        #endregion

        #region IDesignerHost Implementation

        public void Activate()
        {
            var s = (ISelectionService)this.GetService(typeof(ISelectionService));

            // Simply set the root component as the primary selection
            if (s != null)
            {
                var o = new object[] { this.rootComponent };
                s.SetSelectedComponents(o);

                if (this.Activated != null)
                {
                    this.Activated(this, EventArgs.Empty);
                }
            }
        }

        public System.ComponentModel.IComponent CreateComponent(System.Type componentClass, string name)
        {
            IComponent component = null;

            // Create instance
            component = (IComponent)Activator.CreateInstance(componentClass);

            // Add to design container
            this.Add(component, name);

            return component;
        }

        public System.ComponentModel.IComponent CreateComponent(System.Type componentClass)
        {
            return this.CreateComponent(componentClass, null);
        }

        public System.ComponentModel.Design.DesignerTransaction CreateTransaction(string description)
        {
            DesignerTransaction transaction = null;

            // Raise event if this is the first transaction in a chain
            if (this.transactions.Count == 0)
            {
                if (this.TransactionOpening != null)
                {
                    this.TransactionOpening(this, EventArgs.Empty);
                }
            }

            // Create transaction
            if (description == null)
            {
                transaction = new MegaDesignerTransaction(this);
            }
            else
            {
                transaction = new MegaDesignerTransaction(this, description);
            }
            this.transactions.Push(transaction);

            // Let people know a transaction has opened
            if (this.TransactionOpened != null)
            {
                this.TransactionOpened(this, EventArgs.Empty);
            }

            return transaction;
        }

        internal void OnTransactionClosing(bool commit)
        {
            if (this.TransactionClosing != null)
            {
                this.TransactionClosing(this, new DesignerTransactionCloseEventArgs(commit));
            }
        }

        internal void OnTransactionClosed(bool commit)
        {
            if (this.TransactionClosed != null)
            {
                this.TransactionClosed(this, new DesignerTransactionCloseEventArgs(commit));
            }

            this.transactions.Pop();
        }

        public System.ComponentModel.Design.DesignerTransaction CreateTransaction()
        {
            return this.CreateTransaction(null);
        }

        public void DestroyComponent(System.ComponentModel.IComponent component)
        {
            DesignerTransaction t = null;

            // Create transaction
            t = this.CreateTransaction("Destroy Component");

            // Destroy component
            if (component.Site.Container == this)
            {
                this.OnComponentChanging(component, null);
                this.Remove(component);
                component.Dispose();
                this.OnComponentChanged(component, null, null, null);
            }

            // Commit transaction
            t.Commit();
        }

        public System.ComponentModel.Design.IDesigner GetDesigner(System.ComponentModel.IComponent component)
        {
            if (component == null)
            {
                return null;
            }

            return (IDesigner)this.designers[component];
        }

        public System.Type GetType(string typeName)
        {
            var typeResolver = (ITypeResolutionService)this.GetService(typeof(ITypeResolutionService));

            if (typeResolver == null)
            {
                return Type.GetType(typeName);
            }
            else
            {
                return typeResolver.GetType(typeName);
            }
        }

        public System.ComponentModel.IContainer Container
        {
            get
            {
                return this;
            }
        }

        public bool InTransaction
        {
            get
            {
                return (this.transactions.Count > 0);
            }
        }

        public bool Loading
        {
            get
            {
                return false;
            }
        }

        public System.ComponentModel.IComponent RootComponent
        {
            get
            {
                return this.rootComponent;
            }
        }

        public string RootComponentClassName
        {
            get
            {
                return this.rootComponent.GetType().Name;
            }
        }

        public string TransactionDescription
        {
            get
            {
                if (this.InTransaction)
                {
                    var t = (DesignerTransaction)this.transactions.Peek();
                    return t.Description;
                }
                else
                {
                    return null;
                }
            }
        }

        public event System.EventHandler Activated;

        public event System.EventHandler Deactivated;

        public event System.EventHandler LoadComplete;

        public event System.ComponentModel.Design.DesignerTransactionCloseEventHandler TransactionClosed;

        public event System.ComponentModel.Design.DesignerTransactionCloseEventHandler TransactionClosing;

        public event System.EventHandler TransactionOpened;

        public event System.EventHandler TransactionOpening;

        #endregion

        #region IContainer Implementation

        internal bool ContainsName(string name)
        {
            return (this.components.Contains(name));
        }

        public void Add(System.ComponentModel.IComponent component, string name)
        {
            IDesigner designer = null;
            DesignSite site = null;

            // Check we're not trying to add a null component
            if (component == null)
            {
                throw new ArgumentNullException("Cannot add a null component to the container.");
            }

            // Remove this component from its existing container, if applicable
            if (component.Site != null && component.Site.Container != this)
            {
                component.Site.Container.Remove(component);
            }

            // Make sure we have a name for the component
            if (name == null)
            {
                var nameService = (INameCreationService)this.GetService(typeof(INameCreationService));
                name = nameService.CreateName(this, component.GetType());
            }

            // Make sure there isn't already a component with this name in the container
            if (this.ContainsName(name))
            {
                throw new ArgumentException("A component with this name already exists in the container.");
            }

            // Give the new component a site
            site = new DesignSite(this, name);
            site.SetComponent(component);
            component.Site = site;

            // Let everyone know there's a component being added
            if (this.ComponentAdding != null)
            {
                this.ComponentAdding(this, new ComponentEventArgs(component));
            }

            // Get the designer for this component
            if (this.components.Count == 0)
            {
                // This is the first component being added and therefore must offer a root designer
                designer = TypeDescriptor.CreateDesigner(component, typeof(IRootDesigner));
                this.rootComponent = component;
            }
            else
            {
                designer = TypeDescriptor.CreateDesigner(component, typeof(IDesigner));
            }

            // If we got a designer, initialize it
            if (designer != null)
            {
                designer.Initialize(component);
                this.designers[component] = designer;
            }
            else
            {
                // This should never happen
                component.Site = null;
                throw new InvalidOperationException("Failed to get designer for this component.");
            }

            // Add to our list of extenderproviders if necessary
            if (component is IExtenderProvider)
            {
                var e = (IExtenderProviderService)this.GetService(typeof(IExtenderProviderService));
                e.AddExtenderProvider((IExtenderProvider)component);
            }

            // Finally we're able to add the component
            this.components.Add(component.Site.Name, component);
            if (this.ComponentAdded != null)
            {
                this.ComponentAdded(this, new ComponentEventArgs(component));
            }
        }

        public void Add(System.ComponentModel.IComponent component)
        {
            this.Add(component, null);
        }

        public void Remove(System.ComponentModel.IComponent component)
        {
            ISite site = component.Site;
            IDesigner designer = null;

            // Make sure component isn't null
            if (component == null)
            {
                return;
            }

            // Make sure component is sited here
            if (component.Site == null || component.Site.Container != this)
            {
                return;
            }

            // Let the nice people know the component is being removed
            if (this.ComponentRemoving != null)
            {
                this.ComponentRemoving(this, new ComponentEventArgs(component));
            }

            // Remove extender provider (if any)
            if (component is IExtenderProvider)
            {
                var e = (IExtenderProviderService)this.GetService(typeof(IExtenderProviderService));
                e.RemoveExtenderProvider((IExtenderProvider)component);
            }

            // Remove the component and dispose of its designer
            this.components.Remove(site.Name);
            designer = (IDesigner)this.designers[component];
            if (designer != null)
            {
                designer.Dispose();
                this.designers.Remove(component);
            }

            // Let the nice people know the component has been removed
            if (this.ComponentRemoved != null)
            {
                this.ComponentRemoved(this, new ComponentEventArgs(component));
            }

            // Kill the component's site
            component.Site = null;
        }

        public System.ComponentModel.ComponentCollection Components
        {
            get
            {
                var c = new IComponent[] { };

                // If there are no components (shouldn't happen)
                if (this.components.Count == 0)
                {
                    return new ComponentCollection(c);
                }

                // Compile list and return it
                c = new IComponent[this.components.Count];
                this.components.Values.CopyTo(c, 0);
                return new ComponentCollection(c);
            }
        }

        public void Dispose()
        {
            ICollection keys = this.components.Keys;

            // Remove and dispose of all components in our collection
            foreach (string key in keys)
            {
                var component = (IComponent)this.components[key];
                this.components.Remove(component);
                component.Dispose();
            }

            this.components.Clear();
        }

        #endregion

        #region IComponentChangeService Implementation

        public void OnComponentChanged(
            object component, System.ComponentModel.MemberDescriptor member, object oldValue, object newValue)
        {
            if (this.ComponentChanged != null)
            {
                this.ComponentChanged(this, new ComponentChangedEventArgs(component, member, oldValue, newValue));
            }
        }

        public void OnComponentChanging(object component, System.ComponentModel.MemberDescriptor member)
        {
            if (this.ComponentChanging != null)
            {
                this.ComponentChanging(this, new ComponentChangingEventArgs(component, member));
            }
        }

        internal void OnComponentRename(object component, string oldName, string newName)
        {
            if (this.ComponentRename != null)
            {
                this.ComponentRename(this, new ComponentRenameEventArgs(component, oldName, newName));
            }
        }

        public event System.ComponentModel.Design.ComponentEventHandler ComponentAdded;

        public event System.ComponentModel.Design.ComponentEventHandler ComponentAdding;

        public event System.ComponentModel.Design.ComponentChangedEventHandler ComponentChanged;

        public event System.ComponentModel.Design.ComponentChangingEventHandler ComponentChanging;

        public event System.ComponentModel.Design.ComponentEventHandler ComponentRemoved;

        public event System.ComponentModel.Design.ComponentEventHandler ComponentRemoving;

        public event System.ComponentModel.Design.ComponentRenameEventHandler ComponentRename;

        #endregion

        #region IExtenderProvider Implementation

        public bool CanExtend(object extendee)
        {
            return (extendee is IComponent);
        }

        [DesignOnly(true)]
        [Category("Design")]
        [Browsable(true)]
        [ParenthesizePropertyName(true)]
        [Description("The variable used to refer to this component in source code.")]
        public string GetName(IComponent component)
        {
            // Make sure component is sited
            if (component.Site == null)
            {
                throw new InvalidOperationException("Component is not sited.");
            }

            return component.Site.Name;
        }

        public void SetName(IComponent component, string name)
        {
            // Make sure component is sited
            if (component.Site == null)
            {
                throw new InvalidOperationException("Component is not sited.");
            }

            component.Site.Name = name;
        }

        #endregion

        #region ITypeDescriptorFilterService Implementation

        public bool FilterAttributes(IComponent component, IDictionary attributes)
        {
            IDesigner designer = this.GetDesigner(component);
            if (designer is IDesignerFilter)
            {
                ((IDesignerFilter)designer).PreFilterAttributes(attributes);
                ((IDesignerFilter)designer).PostFilterAttributes(attributes);
            }

            return designer == null == false;
        }

        public bool FilterEvents(IComponent component, IDictionary events)
        {
            IDesigner designer = this.GetDesigner(component);
            if (designer is IDesignerFilter)
            {
                ((IDesignerFilter)designer).PreFilterEvents(events);
                ((IDesignerFilter)designer).PostFilterEvents(events);
            }

            return designer == null == false;
        }

        public bool FilterProperties(IComponent component, IDictionary properties)
        {
            IDesigner designer = this.GetDesigner(component);
            if (designer is IDesignerFilter)
            {
                ((IDesignerFilter)designer).PreFilterProperties(properties);
                ((IDesignerFilter)designer).PostFilterProperties(properties);
            }

            return designer == null == false;
        }

        #endregion

        #region IExtenderListService Implementation

        public System.ComponentModel.IExtenderProvider[] GetExtenderProviders()
        {
            // Copy our collection in to an array to return
            var e = new IExtenderProvider[this.extenderProviders.Count];
            this.extenderProviders.CopyTo(e, 0);

            return e;
        }

        #endregion

        #region IExtenderProviderService Implementation

        public void AddExtenderProvider(System.ComponentModel.IExtenderProvider provider)
        {
            if (!this.extenderProviders.Contains(provider))
            {
                this.extenderProviders.Add(provider);
            }
        }

        public void RemoveExtenderProvider(System.ComponentModel.IExtenderProvider provider)
        {
            if (this.extenderProviders.Contains(provider))
            {
                this.extenderProviders.Remove(provider);
            }
        }

        #endregion
    }
}